/*
 * Copyright (c) 2009-2012 Panxiaobo
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package p.cod;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import p.cod.CodFileReader.Clz;
import p.cod.CodFileReader.Field;
import p.cod.CodFileReader.Module;
import p.cod.CodFileReader.Routine;

import com.googlecode.dex2jar.reader.io.DataIn;

public class ReadForFix {
    Map<Integer, Clz> classMap = new HashMap();
    List<Clz> classReferences = new ArrayList();
    DataIn codeIn;
    DataIn dataIn;
    Map<Integer, Object> fixItems = new HashMap();
    CodFileReader reader;

    Module thisModule = new Module();

    public ReadForFix(CodFileReader reader) {
        super();
        this.reader = reader;
        this.codeIn = reader.codeIn;
        this.dataIn = reader.dataIn;
    }

    public void fix() {
        DataIn in = dataIn;
        int attributes = in.readUByte() & 0x77;
        int dataVersion = in.readUByte();
        int icalls = in.readUShortx();
        int modules = in.readUByte();
        int classes = in.readUByte();
        int exported_strings_offset = in.readUShortx();
        int data_bytes = in.readUShortx();

        int initialized_static_data = in.readUShortx();

        int class_definitions = in.readUShortx();

        int type_list_table = in.readUShortx();
        int interface_method_references = in.readUShortx();

        int class_references = in.readUShortx();
        int routine_fixup_table = in.readUShortx();
        int static_routine_fixup_table = in.readUShortx();
        int virtual_routine_fixup_table = in.readUShortx();

        int class_code_fixup_table = in.readUShortx();
        int classDataFixupTable = in.readUShortx();

        int field_fixup_table = in.readUShortx();

        int local_field_fixup_table = in.readUShortx();

        int static_field_fixup_table = in.readUShortx();

        int module_code_fixup_table = in.readUShortx();
        in.skip(14);
        int clzOffset = in.getCurrentPosition();
        for (int i = 0; i < classes; i++) {
            int clz_offset = in.readUShortx();
            Clz clz = readClass4Fix(clz_offset);
            thisModule.addClz(clz);
            thisModule.rountines.addAll(clz.virtualRoutines);
            thisModule.rountines.addAll(clz.staticRoutines);
            thisModule.rountines.addAll(clz.nonVirtualRoutines);
        }
        in.skip(modules * 2 * 2);

        // for (int i = 0; i < modules; i++) {
        // Module m = i == 0 ? thisModule : new Module();
        // int moduleName = in.readUShortx();
        // String s = reader.getLiteralString(moduleName);
        // m.name = s;
        // m.index = i;
        // ms[i] = m;
        // }
        // for (int i = 0; i < modules; i++) {
        // int moduleVersion = in.readUShortx();
        // String s = reader.getLiteralString(moduleVersion);
        // ms[i].version = s;
        // // System.out.println(s);
        // }

        readClassRef(class_references, routine_fixup_table);
        if (dataVersion == 5 || dataVersion == 4) {
            throw new RuntimeException();
        } else {

            readClassDefFixupTable(class_code_fixup_table);
            readModuleFixupTable(module_code_fixup_table);

            readMemeberFixTalbe(dataVersion, routine_fixup_table, true, 1);
            readMemeberFixTalbe(dataVersion, static_routine_fixup_table, true, 2);
            readMemeberFixTalbe(dataVersion, virtual_routine_fixup_table, true, 3);
            readMemeberFixTalbe(dataVersion, field_fixup_table, false, 4);
            readMemeberFixTalbe(dataVersion, static_field_fixup_table, false, 5);
            readMemberLocalFixupTable(local_field_fixup_table, false);
        }

        reader.classReferences = this.classReferences;
        reader.thisModule = thisModule;
        reader.fixItems = this.fixItems;
        this.classMap.clear();
        this.classMap = null;
        this.fixItems = null;
        this.reader = null;
        this.thisModule = null;
    }

    Clz getClassRef(int offset) {
        DataIn in = dataIn;
        in.pushMove(offset + 2);
        try {
            int pkgOffset = in.readUShortx();
            int nameOffset = in.readUShortx();
            return getClz(pkgOffset, nameOffset);
        } finally {
            in.pop();
        }
    }

    Clz getClz(int pkgOffset, int nameOffset) {
        int key = pkgOffset << 16 | nameOffset;
        Clz clz = classMap.get(key);
        if (clz == null) {
            clz = reader.new Clz(pkgOffset, nameOffset);
            classMap.put(key, clz);
        }
        return clz;
    }

    Clz readClass4Fix(int offset) {
        DataIn in = dataIn;
        in.pushMove(offset);
        int pkg_offset = in.readUShortx();
        int name_offset = in.readUShortx();
        Clz clz = getClz(pkg_offset, name_offset);

        in.skip(20);

        int virtualRoutinesOffset = (in.readUShortx() + offset);
        int nonVirtualRoutinesOffset = (in.readUShortx() + offset);
        int staticRoutinesOffset = (in.readUShortx() + offset);
        int fieldDefinitionsOffset = (in.readUShortx() + offset);
        int staticFieldDefinitionsOffset = (in.readUShortx() + offset);
        int implementedInterfacesOffset = (in.readUShortx() + offset);
        in.skip(4);

        readRoutinesForFix(in, virtualRoutinesOffset, nonVirtualRoutinesOffset, clz.virtualRoutines, clz);
        readRoutinesForFix(in, nonVirtualRoutinesOffset, staticRoutinesOffset, clz.nonVirtualRoutines, clz);
        readRoutinesForFix(in, staticRoutinesOffset, fieldDefinitionsOffset, clz.nonVirtualRoutines, clz);

        readFields(in, fieldDefinitionsOffset, staticFieldDefinitionsOffset, false, clz);
        readFields(in, staticFieldDefinitionsOffset, implementedInterfacesOffset, true, clz);
        in.pop();
        return clz;
    }

    private void readClassDefFixupTable(int offset) {
        DataIn in = dataIn;
        in.pushMove(offset);
        int size = in.readUShortx();
        // byte[] buffer = new byte[2];
        while (size-- > 0) {
            skipSlack(in, 2);
            int a = in.readUShortx();
            List<Integer> list = readFixPosition(in);
            Clz clz = getClassRef(a);
            // buffer[0] = (byte) clz.module.index;
            // buffer[1] = (byte) clz.index;
            for (int i : list) {
                // System.arraycopy(buffer, 0, codeBytes, i, 2);
                fixItems.put(i, clz);
            }
        }
        in.pop();
    }

    private void readClassRef(int offset, int end) {
        DataIn in = dataIn;
        in.pushMove(offset);
        try {
            for (int base = in.getCurrentPosition(); base < end; base = in.getCurrentPosition()) {
                int moduleIdx = in.readUShortx();
                int pkgOffset = in.readUShortx();
                int nameOffset = in.readUShortx();
                // String name = this.getString(pkgOffset) + "." + this.getString(nameOffset);
                Clz clz = getClz(pkgOffset, nameOffset);
                classReferences.add(clz);
                int d = in.readUByte();
                int e = in.readUByte();
            }
        } finally {
            in.pop();
        }
    }

    int readFields(DataIn in, int a, int b, boolean isStatic, Clz clz) {
        int size = isStatic ? 6 : 4;
        int s = (b - a) / size;
        if (s > 0) {
            for (int i = 0; i < s; i++) {
                int nameOffset = in.readUShortx();
                int types = in.readUShortx();
                Field f = reader.new Field(nameOffset, types);
                f.index = i;
                f.owner = clz;
                if (isStatic) {
                    int x = in.readUShortx();
                    f.staticPosition = x;
                    clz.staticFieldMap.put(x, f);
                } else {
                    clz.fields.add(f);
                }
            }
        }
        return s;
    }

    List<Integer> readFixPosition(DataIn in) {
        List<Integer> list = new ArrayList();
        int xSize = readLeb(in);
        // in.skip(xSize);
        int base = 0;
        for (int end = in.getCurrentPosition() + xSize, p = in.getCurrentPosition(); p < end; p = in
                .getCurrentPosition()) {
            int delta = readLeb(in);
            base += delta;
            list.add(base);
        }
        return list;
    }

    public int readLeb(DataIn in) {
        int i = 0;
        int j = 0;
        while (true) {
            int k = in.readUByte();
            i = ((k & 0x7F) << j) + i;
            j += 7;
            if ((k & 0x80) == 0)
                break;
        }
        return i & 0xFFFF;
    }

    private void readMemberLocalFixupTable(int offset, boolean isStatic) {
        DataIn in = dataIn;
        in.pushMove(offset);
        int size = in.readUShortx();
        while (size-- > 0) {
            int clzIdx = in.readUByte();

            int fieldIndex = in.readUByte();
            List<Integer> list = readFixPosition(in);
            Clz clz = thisModule.classes.get(clzIdx);
            Field field = (isStatic ? clz.staticFields : clz.fields).get(fieldIndex);
            for (int i : list) {
                // codeBytes[i] = (byte) k;
                fixItems.put(i, field);
            }
        }
        in.pop();
    }

    void readMemeberFixTalbe(int dataVersion, int offset, boolean isRountine, int fixType) {
        DataIn in = dataIn;
        in.pushMove(offset);
        boolean c = isRountine && dataVersion >= 6;
        // int k = c ? 8 : 6;
        int size = in.readShortx();
        if (size < 0) {
            size = -size;
        }
        // fw 4,5
        // f0 >=6
        for (int i = 0; i < size; i++) {
            skipSlack(in, 2);
            int clzRefOffset = in.readUShortx();
            Clz clzRef = getClassRef(clzRefOffset);
            int nameOffset = in.readUShortx();
            String name = reader.getIdentifier(nameOffset);

            int typeListA = in.readUShortx();
            int typeListB = c ? in.readUShortx() : 0;
            Object object = null;
            List<Integer> list = readFixPosition(in);
            switch (fixType) {
            case 1:// routine_fixup_table
            case 2:// static_routine_fixup_table
            case 3:// virtual_routine_fixup_table
            {
                Routine routine = reader.new Routine();
                routine.owner = clzRef;
                routine.name = nameOffset;
                routine.proto = typeListA;
                routine.xreturn = typeListB;
                object = routine;
                break;
            }
            case 4:// field_fixup_table
            case 5:// static_field_fixup_table
            {
                Field field = reader.new Field(nameOffset, typeListA);
                field.owner = clzRef;
                object = field;
                break;
            }
            }
            for (int x : list) {
                fixItems.put(x, object);
            }
        }
        in.pop();
    }

    private void readModuleFixupTable(int offset) {
        DataIn in = dataIn;
        in.pushMove(offset);
        int size = in.readUShortx();
        while (size-- > 0) {
            throw new RuntimeException();
            // skipSlack(in, 2);
            // int u = in.readUByte();
            // List<Integer> list = readFixPosition(in);
            // for (int i : list) {
            // codeBytes[i] = (byte) u;
            // }
        }
        in.pop();
    }

    private Routine readRoutineForFix(int offset) {
        DataIn in = this.codeIn;
        in.pushMove(offset);
        in.push();
        in.move(offset - 5);
        boolean xxxx = in.readUByte() > 1;
        in.pop();
        int headOffset = xxxx ? 9 : 14;
        in.move(offset - headOffset);
        int nameOffset, prototypeOffset, xreturnOffset;
        if (xxxx) {
            xreturnOffset = in.readUShortx();
            prototypeOffset = in.readUShortx();
            in.skip(2);
            nameOffset = in.readUShortx();
        } else {
            nameOffset = in.readUShortx();
            prototypeOffset = in.readUShortx();
            xreturnOffset = in.readUShortx();
        }

        Routine routine = reader.new Routine();
        routine.name = nameOffset;
        routine.proto = prototypeOffset;
        routine.xreturn = xreturnOffset;
        in.pop();
        return routine;
    }

    void readRoutinesForFix(DataIn in, int a, int b, List<Routine> nonVirtualRoutines, Clz clz) {
        int s = (b - a) / 2;
        if (s > 0) {
            for (int i = 0; i < s; i++) {
                int routineOffset = in.readUShortx();
                Routine routine = readRoutineForFix(routineOffset);
                routine.index = i;
                routine.owner = clz;
                routine.offset = routineOffset;
                nonVirtualRoutines.add(routine);
            }
        }
    }

    public final int skipSlack(DataIn in, int paramInt) {
        int i = paramInt - 1;
        int offset = in.getCurrentPosition();
        int j = (offset + i & (i ^ 0xFFFFFFFF)) - offset;
        for (int k = 0; k < j; k++) {
            if (in.readByte() == 0)
                continue;
            throw new RuntimeException("bad slack byte at offset: 0x" + Integer.toHexString(offset));
        }
        return j;
    }
}
